This commit adds support to allow specifying a custom output directory to Cargo.
First, the `build.target-dir` configuration key is checked, and failing that the
`CARGO_TARGET_DIR` environment variable is checked, and failing that the root
package's directory joined with the directory name "target" is used.
There are a few caveats to switching target directories, however:
* If the target directory is in the current source tree, and the folder name is
not called "target", then Cargo may walk the output directory when determining
whether a tree is fresh.
* If the target directory is not called "target", then Cargo may look inside it
currently for `Cargo.toml` files to learn about local packages.
* Concurrent usage of Cargo will still result in badness (#354), and this is now
exascerbated because many Cargo projects can share the same output directory.
* The top-level crate is not cached for future compilations, so if a crate is
built into directory `foo` and then that crate is later used as a dependency,
it will be recompiled.
The naming limitations can be overcome in time, but for now it greatly
simplifies the crawling routines and shouldn't have much of a negative impact
other than some Cargo runtimes (which can in turn be negated by following the
"target" name convention).
Closes #482
pub struct Manifest {
summary: Summary,
targets: Vec<Target>,
- target_dir: PathBuf,
- doc_dir: PathBuf,
links: Option<String>,
warnings: Vec<String>,
exclude: Vec<String>,
version: String,
dependencies: Vec<SerializedDependency>,
targets: Vec<Target>,
- target_dir: String,
- doc_dir: String,
}
impl Encodable for Manifest {
SerializedDependency::from_dependency(d)
}).collect(),
targets: self.targets.clone(),
- target_dir: self.target_dir.display().to_string(),
- doc_dir: self.doc_dir.display().to_string(),
}.encode(s)
}
}
impl Manifest {
pub fn new(summary: Summary, targets: Vec<Target>,
- target_dir: PathBuf, doc_dir: PathBuf,
exclude: Vec<String>,
include: Vec<String>,
links: Option<String>,
Manifest {
summary: summary,
targets: targets,
- target_dir: target_dir,
- doc_dir: doc_dir,
warnings: Vec::new(),
exclude: exclude,
include: include,
}
pub fn dependencies(&self) -> &[Dependency] { self.summary.dependencies() }
- pub fn doc_dir(&self) -> &Path { &self.doc_dir }
pub fn exclude(&self) -> &[String] { &self.exclude }
pub fn include(&self) -> &[String] { &self.include }
pub fn metadata(&self) -> &ManifestMetadata { &self.metadata }
pub fn name(&self) -> &str { self.package_id().name() }
pub fn package_id(&self) -> &PackageId { self.summary.package_id() }
pub fn summary(&self) -> &Summary { &self.summary }
- pub fn target_dir(&self) -> &Path { &self.target_dir }
pub fn targets(&self) -> &[Target] { &self.targets }
pub fn version(&self) -> &Version { self.package_id().version() }
pub fn warnings(&self) -> &[String] { &self.warnings }
pub fn set_summary(&mut self, summary: Summary) {
self.summary = summary;
}
-
- pub fn set_target_dir(&mut self, target_dir: PathBuf) {
- self.target_dir = target_dir;
- }
}
impl Target {
pub fn package_id(&self) -> &PackageId { self.manifest.package_id() }
pub fn root(&self) -> &Path { self.manifest_path.parent().unwrap() }
pub fn summary(&self) -> &Summary { self.manifest.summary() }
- pub fn target_dir(&self) -> &Path { self.manifest.target_dir() }
pub fn targets(&self) -> &[Target] { self.manifest().targets() }
pub fn version(&self) -> &Version { self.package_id().version() }
- pub fn absolute_target_dir(&self) -> PathBuf {
- self.root().join(self.target_dir())
- }
-
pub fn has_custom_build(&self) -> bool {
self.targets().iter().any(|t| t.is_custom_build())
}
opts.config));
try!(src.update());
let root = try!(src.root_package());
- let manifest = root.manifest();
+ let target_dir = opts.config.target_dir(&root);
// If we have a spec, then we need to delete some package,s otherwise, just
// remove the whole target directory and be done with it!
let spec = match opts.spec {
Some(spec) => spec,
- None => return rm_rf(manifest.target_dir()),
+ None => return rm_rf(&target_dir),
};
// Load the lockfile (if one's available), and resolve spec to a pkgid
let pkgs = PackageSet::new(&[]);
let profiles = Profiles::default();
let cx = try!(Context::new(&resolve, &srcs, &pkgs, opts.config,
- Layout::at(root.absolute_target_dir()),
+ Layout::at(target_dir),
None, &pkg, BuildConfig::default(),
&profiles));
// And finally, clean everything out!
for target in pkg.targets().iter() {
// TODO: `cargo clean --release`
- let layout = Layout::new(&root, opts.target, "debug");
+ let layout = Layout::new(opts.config, &root, opts.target, "debug");
try!(rm_rf(&layout.fingerprint(&pkg)));
let profiles = [Profile::default_dev(), Profile::default_test()];
for profile in profiles.iter() {
}
};
- let path = package.absolute_target_dir().join("doc").join(&name)
- .join("index.html");
+ let target_dir = options.compile_opts.config.target_dir(&package);
+ let path = target_dir.join("doc").join(&name).join("index.html");
if fs::metadata(&path).is_ok() {
open_docs(&path);
}
}
let filename = format!("package/{}-{}.crate", pkg.name(), pkg.version());
- let dst = pkg.absolute_target_dir().join(&filename);
+ let target_dir = config.target_dir(&pkg);
+ let dst = target_dir.join(&filename);
if fs::metadata(&dst).is_ok() { return Ok(Some(dst)) }
let mut bomb = Bomb { path: Some(dst.clone()) };
});
let mut new_manifest = pkg.manifest().clone();
new_manifest.set_summary(new_summary.override_id(new_pkgid));
- new_manifest.set_target_dir(dst.join("target"));
let new_pkg = Package::new(new_manifest, &manifest_path, &new_src);
// Now that we've rewritten all our path dependencies, compile it!
use std::path::{PathBuf, Path};
use core::Package;
+use util::Config;
use util::hex::short_hash;
pub struct Layout {
}
impl Layout {
- pub fn new(pkg: &Package, triple: Option<&str>, dest: &str) -> Layout {
- let mut path = pkg.absolute_target_dir();
+ pub fn new(config: &Config, pkg: &Package, triple: Option<&str>,
+ dest: &str) -> Layout {
+ let mut path = config.target_dir(pkg);
// Flexible target specifications often point at filenames, so interpret
// the target triple as a Path and then just use the file stem as the
// component for the directory name.
} else {
deps.iter().find(|p| p.package_id() == resolve.root()).unwrap()
};
- let host_layout = Layout::new(root, None, &dest);
+ let host_layout = Layout::new(config, root, None, &dest);
let target_layout = build_config.requested_target.as_ref().map(|target| {
- layout::Layout::new(root, Some(&target), &dest)
+ layout::Layout::new(config, root, Some(&target), &dest)
});
let mut cx = try!(Context::new(resolve, sources, deps, config,
fn rustdoc(package: &Package, target: &Target, profile: &Profile,
cx: &mut Context) -> CargoResult<Work> {
let kind = Kind::Target;
- let mut doc_dir = cx.get_package(cx.resolve.root()).absolute_target_dir();
let mut rustdoc = try!(process(CommandType::Rustdoc, package, target, cx));
rustdoc.arg(&root_path(cx, package, target))
.cwd(cx.config.cwd())
.arg("--crate-name").arg(&target.crate_name());
+ let mut doc_dir = cx.config.target_dir(cx.get_package(cx.resolve.root()));
if let Some(target) = cx.requested_target() {
rustdoc.arg("--target").arg(target);
doc_dir.push(target);
}
doc_dir.push("doc");
-
- rustdoc.arg("-o").arg(&doc_dir);
+ rustdoc.arg("-o").arg(doc_dir);
match cx.resolve.features(package.package_id()) {
Some(features) => {
use rustc_serialize::{Encodable,Encoder};
use toml;
-use core::MultiShell;
+use core::{MultiShell, Package};
use ops;
use util::{CargoResult, ChainError, internal, human};
cwd: PathBuf,
rustc: PathBuf,
rustdoc: PathBuf,
+ target_dir: Option<PathBuf>,
}
impl Config {
values_loaded: Cell::new(false),
rustc: PathBuf::from("rustc"),
rustdoc: PathBuf::from("rustdoc"),
+ target_dir: None,
};
- cfg.rustc = try!(cfg.get_tool("rustc"));
- cfg.rustdoc = try!(cfg.get_tool("rustdoc"));
- let (rustc_version, rustc_host) = try!(ops::rustc_version(cfg.rustc()));
- cfg.rustc_version = rustc_version;
- cfg.rustc_host = rustc_host;
+ try!(cfg.scrape_tool_config());
+ try!(cfg.scrape_rustc_version());
+ try!(cfg.scrape_target_dir_config());
Ok(cfg)
}
pub fn cwd(&self) -> &Path { &self.cwd }
+ pub fn target_dir(&self, pkg: &Package) -> PathBuf {
+ self.target_dir.clone().unwrap_or_else(|| {
+ pkg.root().join("target")
+ })
+ }
+
pub fn get(&self, key: &str) -> CargoResult<Option<ConfigValue>> {
let vals = try!(self.values());
let mut parts = key.split('.').enumerate();
Ok(())
}
+ fn scrape_tool_config(&mut self) -> CargoResult<()> {
+ self.rustc = try!(self.get_tool("rustc"));
+ self.rustdoc = try!(self.get_tool("rustdoc"));
+ Ok(())
+ }
+
+ fn scrape_rustc_version(&mut self) -> CargoResult<()> {
+ let (rustc_version, rustc_host) = try!(ops::rustc_version(&self.rustc));
+ self.rustc_version = rustc_version;
+ self.rustc_host = rustc_host;
+ Ok(())
+ }
+
+ fn scrape_target_dir_config(&mut self) -> CargoResult<()> {
+ if let Some((dir, dir2)) = try!(self.get_string("build.target-dir")) {
+ let mut path = PathBuf::from(dir2);
+ path.pop();
+ path.pop();
+ path.push(dir);
+ self.target_dir = Some(path);
+ } else if let Some(dir) = env::var_os("CARGO_TARGET_DIR") {
+ self.target_dir = Some(self.cwd.join(dir));
+ }
+ Ok(())
+ }
+
fn get_tool(&self, tool: &str) -> CargoResult<PathBuf> {
let var = format!("build.{}", tool);
if let Some((tool, path)) = try!(self.get_string(&var)) {
let profiles = build_profiles(&self.profile);
let mut manifest = Manifest::new(summary,
targets,
- layout.root.join("target"),
- layout.root.join("doc"),
exclude,
include,
project.links.clone(),
timeout = 60000 # Timeout for each HTTP request, in milliseconds
[build]
-jobs = 1 # number of jobs to run by default (default to # cpus)
-rustc = "rustc" # path to the compiler to execute
-rustdoc = "rustdoc" # path to the doc generator to execute
+jobs = 1 # number of jobs to run by default (default to # cpus)
+rustc = "rustc" # path to the compiler to execute
+rustdoc = "rustdoc" # path to the doc generator to execute
+target-dir = "target" # path of where to place all generated artifacts
```
# Environment Variables
compiler instead.
* `RUSTDOC` - Instead of running `rustdoc`, Cargo will execute this specified
`rustdoc` instance instead.
+* `CARGO_TARGET_DIR` - Location of where to place all generated artifacts,
+ relative to the current working directory.
assert_that(p.cargo("build"),
execs().with_status(0));
});
+
+test!(custom_target_dir {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#)
+ .file("src/main.rs", "fn main() {}");
+ p.build();
+
+ let exe_name = format!("foo{}", env::consts::EXE_SUFFIX);
+
+ assert_that(p.cargo("build").env("CARGO_TARGET_DIR", "foo/target"),
+ execs().with_status(0));
+ assert_that(&p.root().join("foo/target/debug").join(&exe_name),
+ existing_file());
+ assert_that(&p.root().join("target/debug").join(&exe_name),
+ is_not(existing_file()));
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0));
+ assert_that(&p.root().join("foo/target/debug").join(&exe_name),
+ existing_file());
+ assert_that(&p.root().join("target/debug").join(&exe_name),
+ existing_file());
+
+ fs::create_dir(p.root().join(".cargo")).unwrap();
+ File::create(p.root().join(".cargo/config")).unwrap().write_all(br#"
+ [build]
+ target-dir = "bar/target"
+ "#).unwrap();
+ assert_that(p.cargo("build").env("CARGO_TARGET_DIR", "foo/target"),
+ execs().with_status(0));
+ assert_that(&p.root().join("bar/target/debug").join(&exe_name),
+ existing_file());
+ assert_that(&p.root().join("foo/target/debug").join(&exe_name),
+ existing_file());
+ assert_that(&p.root().join("target/debug").join(&exe_name),
+ existing_file());
+});